'''
作品名：MinecraftSkinDownloader
Github仓库地址:https://github.com/NewbieXvwu/MinecraftSkinDownloader
Gitee仓库地址:https://gitee.com/NewbieXvwu/MinecraftSkinDownloader
关于本程序：这是一个可以简单地下载任何Minecraft正版玩家的皮肤的软件，使用Python编写，由NewbieXvwu维护。
作者：NewbieXvwu
'''
version_int = 2.7  # 程序主版本号
ispreview = False  # 程序是否是预览版
previewversion = "0"  # 预览版本号（不自动更新）
if ispreview:  # 生成字符串版的版本号
    version = "v" + str(version_int) + " Preview " + previewversion
else:
    version = "v" + str(version_int)

# 导入本地库
import ctypes, sys
# import imp
import os
import json
import base64
import tkinter
from urllib.request import urlretrieve
import zipfile
import shutil
from tkinter import messagebox
import threading
import platform
import ctypes
import time

try:
    import winreg  # Windows注册表操作库
except:
    pass


def isLinux():
    """判断当前操作系统是否为Linux"""
    return platform.architecture()[1] == "ELF"


global ThreadShouldStop  # 全局变量，用于控制线程停止
ThreadShouldStop = False


def on_closing():
    """关闭窗口时的回调函数，提示用户不要强制退出"""
    tkinter.messagebox.showerror("错误", "正在安装必要的运行库！\n强制退出会造成运行库损坏！")


# 尝试安装并导入第三方库
try:
    import requests  # HTTP请求库
except:  # 没有安装requests
    from tkinter import *
    import tkinter
    from tkinter.ttk import *


    def sc_main_():
        """显示安装运行库的GUI界面"""
        sc_ = Tk()
        # 窗口居中
        scw = sc_.winfo_screenwidth()
        sch = sc_.winfo_screenheight()
        w = 300
        h = 200
        x = (scw - w) / 2
        y = (sch - h) / 2
        sc_.title("正在安装运行库")
        sc_.geometry("%dx%d+%d+%d" % (w, h, x, y))
        sc_.maxsize(w, h)
        sc_.minsize(w, h)

        try:  # 从双源尝试下载Logo
            sc_.iconbitmap('logo.ico')
        except:
            try:
                urlretrieve("https://gitee.com/NewbieXvwu/MinecraftSkinDownloader/raw/main/logo.ico", "logo.ico")
                sc_.iconbitmap('logo.ico')
            except:
                urlretrieve("https://github.com/NewbieXvwu/MinecraftSkinDownloader/raw/main/logo.ico", "logo.ico")
                sc_.iconbitmap('logo.ico')

        try:  # 高DPI适配
            ctypes.windll.shcore.SetProcessDpiAwareness(1)
            ScaleFactor = ctypes.windll.shcore.GetScaleFactorForDevice(0)
            sc_.tk.call('tk', 'scaling', ScaleFactor / 75)
        except:
            pass

        def showmain():
            """进度条动画显示函数"""
            while True:
                for i in range(100):
                    try:
                        # 每次更新加1
                        pb_['value'] = i + 1
                        # 更新画面
                        sc_.update()
                        time.sleep(0.05)
                    except:
                        exit()
                for i in range(100):
                    try:
                        # 每次更新减1
                        pb_['value'] = 100 - i
                        # 更新画面
                        sc_.update()
                        time.sleep(0.05)
                    except:
                        exit()

        def ThreadStop():
            """监控线程停止标志，强制结束Python进程"""
            while True:
                try:
                    if ThreadShouldStop:
                        try:
                            # 结束所有可能的Python进程
                            os.system("taskkill -f -im python.exe")
                            os.system("taskkill -f -im pythonw.exe")
                            os.system("taskkill -f -im py.exe")
                            os.system("taskkill -f -im pyw.exe")
                            os.system("taskkill -f -im python3.exe")
                            os.system("taskkill -f -im pythonw3.exe")
                            os.system("taskkill -f -im py3.exe")
                            os.system("taskkill -f -im pyw3.exe")
                        except:
                            exit()
                except:
                    pass
                time.sleep(0.1)

        run___ = threading.Thread(target=ThreadStop)
        run___.daemon = True  # 设置为守护线程
        run___.start()

        def show():  # 多线程运行主函数，防止主线程GUI卡死
            run__ = threading.Thread(target=showmain)
            run__.start()

        # GUI组件定义和布局
        lb1_ = Label(sc_, text="正在安装程序必要的运行库……", font=("宋体", 13))
        lb1_.place(x=30, y=30)
        lb2_ = Label(sc_, text="  正在安装：requests\n\n安装完毕后请手动重启程序", font=("宋体", 10))
        lb2_.place(x=150, y=90, anchor="center")
        pb_ = Progressbar(sc_, length=240, mode='indeterminate', orient=tkinter.HORIZONTAL)
        pb_.place(x=30, y=130)

        show()  # 启动进度条动画
        sc_.protocol('WM_DELETE_WINDOW', on_closing)  # 设置关闭窗口事件处理
        sc_.mainloop()


    run___ = threading.Thread(target=sc_main_)
    run___.start()

    # 使用清华镜像源安装requests库
    result = os.popen("pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests").read()
    if "Successfully installed" in result:  # 安装requests成功
        import requests
        from requests import delete

        ThreadShouldStop = True  # 设置停止标志，结束安装线程
    else:  # 安装requests失败
        if tkinter.messagebox.askyesno("错误",
                                       "运行库安装失败，程序无法继续运行！\n请把以下内容提交给开发者：\n" + result + "\n是否要提交错误？"):
            os.startfile(
                "https://github.com/NewbieXvwu/MinecraftSkinDownloader/issues/new?assignees=&labels=bug&template=bug_report.yml&title=%5B%E6%BC%8F%E6%B4%9E%5D+%E6%97%A0%E6%B3%95%E5%AE%89%E8%A3%85%E4%BE%9D%E8%B5%96%E5%BA%93")
        exit()


def validate_number():
    """验证输入框内容，设置输入验证状态"""
    while True:
        if e.get() == "":
            e.state(["invalid"])  # 输入为空，设置为无效状态
        else:
            e.state(["!invalid"])  # 输入不为空，取消无效状态
        time.sleep(0.1)


# 定义函数
def getzbmain():  # 主函数
    """下载Minecraft皮肤的主逻辑函数"""
    id_ = e.get()  # 获取输入的用户名
    if id_ == "":
        tkinter.messagebox.showerror(title='错误', message='请填写内容！')
        e.focus_set()  # 焦点设置到输入框
    else:
        zt.set("状态：正在向Mojang请求玩家的UUID……")

        # 第一步：获取玩家UUID
        url1 = "https://api.mojang.com/users/profiles/minecraft/" + id_
        r = requests.get(url1)
        del url1
        status_code = r.status_code
        if not status_code == 200:
            zt.set("状态：Bugjump出现错误，请检查你的输入！")
        del status_code

        zt.set("状态：读取UUID中……")
        r = r.text
        r = json.loads(r)
        try:
            uuid = r['id']  # 提取UUID
        except:
            zt.set("状态：Bugjump出现错误，请检查你的输入！")
        del r

        # 第二步：获取皮肤下载地址
        zt.set("状态：向Mojang请求下载皮肤的地址中……")
        url2 = "https://sessionserver.mojang.com/session/minecraft/profile/" + uuid
        r = requests.get(url2)
        del url2
        status_code = r.status_code
        if not status_code == 200:
            zt.set("状态：Bugjump出现错误，请检查你的输入！")
        del status_code

        zt.set("状态：读取皮肤下载地址中……")
        r = r.text
        r = json.loads(r)
        properties = r["properties"]
        del r
        properties = properties[0]
        properties = properties["value"]

        zt.set("状态：解码皮肤下载地址中……")
        properties = base64.b64decode(properties)  # Base64解码
        properties = properties.decode()
        properties = json.loads(properties)
        url3 = properties["textures"]
        del properties

        # 检查并下载披风
        havecape = False
        try:
            cape = url3["CAPE"]
            cape = cape["url"]
            filename = id_ + '_cape.png'
            zt.set("状态：成功获取披风下载直链，正在尝试下载……")
            urlretrieve(cape, filename)  # 下载披风文件
            havecape = True
        except:
            pass

        # 下载皮肤
        url3 = url3["SKIN"]
        try:
            isalex = url3["metadata"]
            isalex = isalex["model"]  # 检查是否为Alex模型（细手臂）
        except:
            isalex = ""
        url3 = url3["url"]
        filename = id_ + '.png'
        zt.set("状态：成功获取皮肤下载直链，正在尝试下载……")
        urlretrieve(url3, filename)  # 下载皮肤文件
        del url3
        del filename

        # 下载完成提示
        if havecape:
            exit_ = str(
                tkinter.messagebox.showwarning(title="下载完毕", message="下载完毕！此玩家还拥有披风，已同时下载！"))
        else:
            exit_ = str(tkinter.messagebox.showwarning(title="下载完毕", message="下载完毕！"))

        zt.set("状态：待命")
        lb2.config(textvariable=zt)

        # 非Linux系统提供材质包打包功能
        if not isLinux:
            exit_ = tkinter.messagebox.askyesno(title="下载完毕", message="下载完毕！按"
            确认
            "打包皮肤成材质包，或者按"
            取消
            "打开文件！")
            if exit_ == True:
                # 创建材质包流程
                try:
                    file = ".\\" + id_
                    shutil.rmtree(file)  # 删除旧目录
                    del file
                except:
                    zt.set("状态：正在删除旧的临时目录……")

                # 创建材质包目录结构
                zt.set("状态：正在创建新的临时目录……")
                lb2.config(textvariable=zt)
                file = "./" + id_
                os.mkdir(file)
                del file

                # 创建pack.mcmeta文件
                zt.set("状态：正在创建材质包说明文件……")
                lb2.config(textvariable=zt)
                filename = './' + id_ + "/pack.mcmeta"
                with open(filename, 'w') as file_object:
                    file_object.write("{"
                    pack
                    ":{"
                    pack_format
                    ":4,"
                    description
                    ":"§cSkin
                    Resourcepack
                    "}}")
                    del filename

                    # 下载材质包图标
                    zt.set("状态：正在下载材质包Logo……")
                    lb2.config(textvariable=zt)
                    url3 = "https://pic.downk.cc/item/5ff174673ffa7d37b35bb165.png"
                    filename = "./" + id_ + "/pack.png"
                    urlretrieve(url3, filename)
                    del url3
                    del filename

                    # 创建Minecraft资源包目录结构
                    zt.set("状态：正在创建皮肤目录……")
                    lb2.config(textvariable=zt)
                    directories = [
                        "./" + id_ + "/assets",
                        "./" + id_ + "/assets/minecraft",
                        "./" + id_ + "/assets/minecraft/textures",
                        "./" + id_ + "/assets/minecraft/textures/entity"
                    ]
                    for dir_path in directories:
                        os.mkdir(dir_path)

                    # 复制皮肤文件到材质包
                    zt.set("状态：正在复制皮肤文件……")
                    lb2.config(textvariable=zt)
                    if isalex == "slim":
                        cmd = "copy " + id_ + ".png .\\" + id_ + "\\assets\\minecraft\\textures\\entity\\alex.png"
                        os.system(cmd)
                    else:
                        cmd = "copy " + id_ + ".png .\\" + id_ + "\\assets\\minecraft\\textures\\entity\\steve.png"
                        os.system(cmd)
                    del cmd

                    # 压缩材质包
                    zt.set("状态：正在压缩材质包……")
                    lb2.config(textvariable=zt)
                    shutil.make_archive("Skin_" + id_, 'zip', id_)

                    # 清理临时文件
                    zt.set("状态：正在删除临时目录……")
                    lb2.config(textvariable=zt)
                    file = ".\\" + id_
                    shutil.rmtree(file)
                    del file

                    zt.set("状态：待命")

                    # 检测Minecraft安装并导入材质包
                    if os.path.exists(".\\.minecraft\\resourcepacks"):
                        exit_ = tkinter.messagebox.askyesno(title="创建材质包成功",
                                                            message="成功创建材质包！\n注意：材质包会将游戏内的所有玩家的皮肤都替换成你想要的皮肤，可能会导致一些小问题！\n检测到程序目录下有Minecraft安装，如果要直接导入Minecraft，请按下"
                        确认
                        "，否则请按下"
                        取消
                        "打开材质包。")
                        if exit_ == True:
                            cmd = "copy Skin_" + id_ + ".zip .\\.minecraft\\resourcepacks\\" + id_ + ".zip"
                            os.system(cmd)
                            del cmd
                            exit_ = tkinter.messagebox.askyesno(title="导入成功",
                                                                message="导入成功！\n是否要打开材质包文件夹？")
                            if exit_ == True:
                                start = "start \"\" .\\.minecraft\\resourcepacks\\"
                                os.system(start)
                            del exit_
                        else:
                            start = "start \"\" " + "\"" + id_ + '.zip' + "\""
                            os.system(start)
                    else:
                        exit_ = tkinter.messagebox.askyesno(title="创建材质包成功",
                                                            message="创建材质包成功！注意：材质包会将游戏内的所有玩家的皮肤都替换成你想要的皮肤，可能会导致一些小问题！\n是否要打开材质包？")
                        if exit_ == True:
                            start = "start \"\" " + "\"Skin_" + id_ + '.zip' + "\""
                            os.system(start)
                        del exit_
                else:
                # 直接打开皮肤文件
                try:
                    start = id_ + '.png'
                    os.startfile(start)
                except:
                    pass


def getzb(ev=None):  # 多线程运行主函数，防止主线程GUI卡死
    """启动皮肤下载线程"""
    run_ = threading.Thread(target=getzbmain)
    run_.start()


def info():  # 关于页面
    """显示关于程序信息的窗口"""

    def opengithub():
        os.startfile("https://github.com/NewbieXvwu/MinecraftSkinDownloader")

    def opengitee():
        os.startfile("https://gitee.com/NewbieXvwu/MinecraftSkinDownloader")

    def openbilibili():
        os.startfile("https://space.bilibili.com/505201154")

    about = Toplevel()  # 创建顶级窗口
    about.title("关于本程序")

    # 窗口居中
    aboutscw = about.winfo_screenwidth()
    aboutsch = about.winfo_screenheight()
    aboutw = 300
    abouth = 210
    aboutx = (aboutscw - aboutw) / 2
    abouty = (aboutsch - abouth) / 2
    about.geometry("%dx%d+%d+%d" % (aboutw, abouth, aboutx, abouty))

    try:
        about.iconbitmap('logo.ico')
    except:
        pass

    about.resizable(width=False, height=False)  # 禁止调整窗口大小

    # 关于页面GUI组件
    lb4 = Label(about, text="关于本程序", font=("宋体", 15))
    lb4.place(x=100, y=30)

    if isLinux():
        lb5 = Label(about, text="   这是一个可以简单地下载任何\n Minecraft正版玩家皮肤的软件。", font=("宋体", 14))
    else:
        lb5 = Label(about, text=" 这是一个可以简单地下载任何\n\n Minecraft正版玩家皮肤的软件。", font=("宋体", 14))
    lb5.place(x=150, y=100, anchor=CENTER)

    # 链接按钮
    btn3 = Button(about, text="Github", command=opengithub)
    btn3.place(x=210, y=155)
    btn4 = Button(about, text="Gitee", command=opengitee)
    btn4.place(x=118, y=155)
    btn5 = Button(about, text="Bilibili", command=openbilibili)
    btn5.place(x=20, y=155)

    about.lift()  # 窗口置顶


# 主程序GUI初始化
from tkinter import *
from tkinter.ttk import *

sc = Tk()
sc.tk.call("source", "sun-valley.tcl")  # 加载Sun Valley主题
sc.tk.call("set_theme", "light")  # 设置浅色主题

# 窗口居中
scw = sc.winfo_screenwidth()
sch = sc.winfo_screenheight()
w = 500
h = 300
x = (scw - w) / 2
y = (sch - h) / 2
sc.title("Minecraft正版皮肤下载器" + version + " By 萌新欻無")
sc.geometry("%dx%d+%d+%d" % (w, h, x, y))
sc.maxsize(w, h)
sc.minsize(w, h)
sc.resizable(width=False, height=False)

try:  # 设置窗口图标
    sc.iconbitmap('logo.ico')
except:
    try:
        urlretrieve("https://gitee.com/NewbieXvwu/MinecraftSkinDownloader/raw/main/logo.ico", "logo.ico")
        sc.iconbitmap('logo.ico')
    except:
        try:
            urlretrieve("https://github.com/NewbieXvwu/MinecraftSkinDownloader/raw/main/logo.ico", "logo.ico")
            sc.iconbitmap('logo.ico')
        except:
            pass

try:  # Windows高DPI适配
    ctypes.windll.shcore.SetProcessDpiAwareness(1)
    ScaleFactor = ctypes.windll.shcore.GetScaleFactorForDevice(0)
    sc.tk.call('tk', 'scaling', ScaleFactor / 75)
except:
    pass

# 主屏幕组件初始化
lb1 = Label(sc, text="请输入Minecraft正版账号名称", font=("宋体", 15))
lb1.place(x=110, y=50)

e = Entry(sc, width=20)  # 用户名输入框
if isLinux():
    e.place(x=160, y=120)
else:
    e.place(x=170, y=120)
e.bind("<Return>", getzb)  # 绑定回车键事件
e.focus_set()  # 设置焦点

# 启动输入验证线程
run____ = threading.Thread(target=validate_number)
run____.start()

# 下载按钮
btn1 = Button(sc, text="点击获取", command=getzb, style="Accent.TButton")
btn1.place(x=210, y=190)

# 状态显示标签
zt = tkinter.StringVar()
zt.set("状态：待命")
lb2 = Label(sc, textvariable=zt, font=("宋体", 15))
if isLinux():
    lb2.place(x=10, y=265)
else:
    lb2.place(x=10, y=270)

# 关于按钮
btn2 = Button(sc, text="关于", command=info)
if isLinux():
    btn2.place(x=440, y=255)
else:
    btn2.place(x=440, y=260)

# 版本号标签
lb3 = Label(sc, text=version, font=("宋体", 10))
lb3.place(x=5, y=5)

# 主题切换菜单
cmb = Menubutton(sc, width=7)
if isLinux():
    cmb.place(x=395, y=5)
else:
    cmb.place(x=400, y=5)


def light():
    """切换到浅色主题"""
    cmb.config(text="浅色模式")
    sc.tk.call("set_theme", "light")


def dark():
    """切换到深色主题"""
    cmb.config(text="深色模式")
    sc.tk.call("set_theme", "dark")


filemenu = tkinter.Menu(cmb, tearoff=False)
filemenu.add_command(label="浅色模式", command=light)
filemenu.add_command(label="深色模式", command=dark)
cmb.config(menu=filemenu)

try:  # 读取Windows 10深色模式设置
    key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize")
    i = 0
    while True:
        name, value, type = winreg.EnumValue(key, i)
        if str(name) == "AppsUseLightTheme":
            break
        i += 1
    if value == 0:  # 深色模式
        dark()
    else:  # 浅色模式
        light()
except:
    pass

# 操作系统和Python版本检查
try:
    if float(str(platform.version().split(".")[0]) + "." + str(platform.version().split(".")[1])) > 6.3 and int(
            platform.python_version().split(".")[1]) <= 8:
        if tkinter.messagebox.askyesno(title="您正在使用过旧的Python", message="您的操作系统为Windows " + str(
                platform.version().split(".")[
                    0]) + "，\n但本程序正运行在版本为" + platform.python_version() + "的Python上。\n这可能是因为您下载了本程序的Windows 7特别版。\n使用特别版将会导致程序的稳定性无法得到保证，因为本程序的开发使用了更新的Python版本。\n您是否要下载一个稳定性更好的版本？"):
            os.startfile("https://github.com/NewbieXvwu/MinecraftSkinDownloader/releases")
            exit()
except:
    pass

# 预览版警告
if ispreview:
    if not tkinter.messagebox.askyesno(title="您正在使用预览版",
                                       message="您正在使用的版本为" + version + "，这是一个预览版。\n使用预览版可能会带来一些不可预知的问题！\n您是否要继续？"):
        os.startfile("https://github.com/NewbieXvwu/MinecraftSkinDownloader/releases")
        exit()

# 主循环
sc.mainloop()